-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local class = require 'mc.class'
local log = require 'mc.logging'
local file_checker = require 'utils.file'
local object_manage = require 'mc.mdb.object_manage'
local reboot_manage = require 'mc.mdb.micro_component.reboot'
local debug_manage = require 'mc.mdb.micro_component.debug'
local base_service = require 'bmc_datasync.service'
local canbus_collection = require 'canbus.collection'
local object_mgmt = require 'objects.manager'
local data_mgmt = require 'data_management'
local consts = require 'constants'
local sender_instance = require 'tcp.sender'
local vos = require 'utils.vos'

local datasync_service = class(base_service)

local function check_debug_enabled()
    -- The initialize will return while the debug service fails to load
    local ok, msg = pcall(require, 'bmc_datasync.debug.service')
    if not ok then
        log:notice('debug service fails to load, error: %s', msg)
        return false
    end
    local debug_service = msg
    if not debug_service then
        log:notice('debug service is invalid')
        return false
    end

    -- The initialize will return while the debug library fails to load
    ok, msg = pcall(require, 'debug.collection')
    if not ok then
        log:notice('canbus testing library fails to load, error: %s', msg)
        return false
    end
    local collection = msg
    if not collection then
        log:notice('debug handler collection is invalid')
        return false
    end

    return true, debug_service, collection
end

function datasync_service:init_debug_handler()
    local ok, service, collection = check_debug_enabled()
    if not ok then
        return
    end

    local function wrapper(command, ...)
        local handler = collection.get_handler(command)
        if handler then
            return handler(...)
        end
    end

    -- implement the methods for debug library
    local debug_service = class(service)
    function debug_service:implement_methods()
        self:ImplRemoteAccessCANSendTestBoardcast(function(obj, ctx, channel)
            return wrapper(collection.cmds.CHANNEL_BOARDCAST, channel)
        end)
        self:ImplRemoteAccessCANPrintMetrics(function(obj, ctx, channel)
            return wrapper(collection.cmds.CHANNEL_METRICS, channel)
        end)
        self:ImplRemoteAccessRemoteAccessGetRemoteProperties(function(obj, ctx, ...)
            return wrapper(collection.cmds.REMOTE_PROPERTIES, self.bus, ...)
        end)
        self:ImplRemoteAccessRemoteAccessRemoteCall(function(obj, ctx, ...)
            return wrapper(collection.cmds.REMOTE_METHOD, self.bus, ...)
        end)
    end

    -- create debug service instance
    local service_instance = debug_service.new(self.bus)
    service_instance:implement_methods()
end

function datasync_service:ctor()
    log:notice('bmc_datasync app is created')
    -- do nothing
end

function datasync_service:exit()
    canbus_collection.get_instance():close()
    log:notice('bmc_datasync app exit completed')
end

-- 对象注册有时序依赖，因此先缓存对象
local cache_obj = {
    ['CANMgmt'] = nil,
    ['PropertySync'] = nil,
    ['TCPMgmt'] = nil,
    ['SyncDestination'] = {},
    ['FileSync'] = {}
}

function datasync_service:cache_register(class_name, cb)
    if class_name == 'SyncDestination' or class_name == 'FileSync' then
        for _, object in ipairs(cache_obj[class_name]) do
            cb(object)
        end
        return
    end
    if cache_obj[class_name] then
        cb(cache_obj[class_name])
    end
end

function datasync_service:register_can_mgmt(object)
    canbus_collection.get_instance():register(self.bus, object)
    if canbus_collection.get_instance().canbus_enabled then
        -- register procedure callbacks
        self:register_procedure_callbacks()
    end
end

function datasync_service:register_datasync(object)
    if not self.obj_mgmt:register_datasync(object) then
        return
    end
    -- 初始化RPC接口
    self:init_rpc_method()
    -- 根据通信类型进行初始化
    local communication_type = self.obj_mgmt.data_sync_object.communication_type
    if communication_type == consts.COMMUNICATION_TYPE_CAN then
        self:cache_register('CANMgmt', function (o)
            self:register_can_mgmt(o)
        end)
    else
        self:cache_register('TCPMgmt', function (o)
            self.obj_mgmt:register_tcp_mgmt(o)
        end)
        self:cache_register('SyncDestination', function (o)
            self.obj_mgmt:register_sync_destination(o)
        end)
        self:cache_register('FileSync', function (o)
            self.obj_mgmt:register_file_sync(o)
        end)
    end
    -- 注册信号接口
    data_mgmt.get_instance().emit_signal_cb = function(...)
        self:DataSyncDataSyncPropertyUpdated(object, ...)
    end
    data_mgmt.get_instance():register()
    self:cache_register('PropertySync', function (o)
        self.obj_mgmt:register_property_sync(o, communication_type)
    end)
    cache_obj = nil
end

function datasync_service:register_object_callbacks()
    object_manage.on_add_object(self.bus, function(class_name, object, position)
        if class_name == 'DataSync' then
            self:register_datasync(object)
        elseif not self.obj_mgmt.data_sync_object then
            -- 部分对象存在多个
            if class_name == 'SyncDestination' or class_name == 'FileSync' then
                table.insert(cache_obj[class_name], object)
            else
                cache_obj[class_name] = object
            end
        elseif class_name == 'PropertySync' then
            self.obj_mgmt:register_property_sync(object, self.obj_mgmt.data_sync_object.communication_type)
        elseif class_name == 'CANMgmt' and self.obj_mgmt.data_sync_object.communication_type == consts.COMMUNICATION_TYPE_CAN then
            self.obj_mgmt:register_can_mgmt(object)
        elseif self.obj_mgmt.data_sync_object.communication_type == consts.COMMUNICATION_TYPE_CAN then
            -- 如果是CAN通信，依赖TCP通信的对象都不需要注册了
            return
        elseif class_name == 'TCPMgmt' then
            self.obj_mgmt:register_tcp_mgmt(object)
        elseif class_name == 'SyncDestination' then
            self.obj_mgmt:register_sync_destination(object)
        elseif class_name == 'FileSync' then
            self.obj_mgmt:register_file_sync(object)
        end
    end)
end

local reboot_time = 0  -- 重启时统计总耗时，因为属性同步时阻塞式的，当前未分帧，所以直接统计总耗时即可，统计完清0

function datasync_service:register_reboot_callbacks()
    reboot_manage.on_prepare(function (...)
        reboot_time = vos.vos_tick_get()
        canbus_collection.get_instance():close()
        log:notice('bmc_datasync reboot prepare completed')
        return 0
    end)
    reboot_manage.on_action(function (...)
        local res = sender_instance:get_instance():reboot_process()
        if vos.vos_tick_get() - reboot_time > 45 then  -- 重启阶段基线45秒，超时输入原因
            log:warn('reboot failed, [%s]', res)
        end
        reboot_time = 0
        log:notice('bmc_datasync reboot action completed')
        return 0
    end)
    reboot_manage.on_cancel(function (...)
        -- NOTE: need re-initialize the canbus while reboot prepare is done then cancelled
        reboot_time = 0
        log:notice('bmc_datasync reboot is cancelled')
    end)
end

function datasync_service:init_rpc_method()
    self:ImplDataSyncDataSyncGetProperties(function(_, _, identifier, object_name, interface)
        return data_mgmt.get_instance():get_sync_property(identifier, object_name, interface)
    end)
end

function datasync_service:register_procedure_callbacks()
    -- register heartbeat via canbus procedure callbacks
    local callbacks = require 'canbus.heartbeat'
    if not callbacks then
        log:error('canbus heartbeat submodule is missing, register failed.')
    else
        callbacks.register()
    end

    -- register remote call via canbus procedure callbacks
    callbacks = require 'canbus.remote_access'
    if not callbacks then
        log:error('canbus remote access submodule is missing, register failed')
    else
        callbacks.register(self.bus)
    end
    log:notice('bmc_datasync register procedure callbacks completed')
end

function datasync_service:register_debug_callbacks()
    debug_manage.on_dump(function(ctx, path)
        log:notice('dump start')

        -- validate the file and open it
        local ok = file_checker.check_realpath_before_open_s(path)
        if ok ~= 0 then
            log:error('bmc_datasync dump failed, error: path is invalid, code: %s', ok)
            return
        end
        ok = file_checker.check_shell_special_character_s(path)
        if ok ~= 0 then
            log:error('bmc_datasync dump failed, error: path has special chars, code: %s', ok)
            return
        end

        -- dump canbus metrics
        canbus_collection.get_instance():dump_canbus(path)
        -- dump property sync
        data_mgmt.get_instance():dump(path)
        log:notice('dump end')
    end)
end

function datasync_service:init()
    self.super.init(self)

    -- init object management
    self.obj_mgmt = object_mgmt.get_instance(self.bus)

    -- register discovery object callbacks
    self:register_object_callbacks()

    -- register component debug callbacks
    self:register_debug_callbacks()

    -- register component reboot callbacks
    self:register_reboot_callbacks()

    -- initialize application debug handler
    self:init_debug_handler()
    log:notice('bmc_datasync app is initialized.')
end

return datasync_service